home *** CD-ROM | disk | FTP | other *** search
/ Shareware Grab Bag / Shareware Grab Bag.iso / 090 / toolfix.arc / FLUSH.ACC < prev    next >
Text File  |  1987-03-03  |  14KB  |  300 lines

  1. *******************************************************************************
  2.  
  3.                                    FLUSH.ACC
  4.                                   Version 1.3
  5.                                February 10, 1986
  6.                                by Randy Forgaard
  7.                              CompuServe 70307,521
  8.  
  9.  
  10. This file, FLUSH.ACC, is related to the file FLUSH.PAS in DL 1 of the Borland
  11. SIG on CompuServe, but FLUSH.PAS is not needed in order to use the routines
  12. below.
  13.  
  14. This file is for use with the Turbo Access portion of the MS-DOS Turbo
  15. (Database) Toolbox Version 1.2, together with MS-DOS or PC-DOS Turbo Pascal
  16. 3.01A or higher (regular, 8087, or BCD version).  If you have Version 1.00,
  17. 1.01, or 1.1 of the Toolbox, see the file TBXFIX in DL 1 of the Borland SIG for
  18. the source code changes that will upgrade your copy to Version 1.2 (plus
  19. additional fixes).  If you have Version 3.00A or 3.00B of Turbo Pascal, arrange
  20. with Borland Customer Service to upgrade your copy of Turbo to 3.01A or higher
  21. (3.00A and 3.00B are not compatible with Turbo Database Toolbox).  The routines
  22. below will not work with earlier (pre-3.0) versions of Turbo Pascal, nor with
  23. Turbo Pascal for operating systems other than DOS.
  24.  
  25. This file describes certain changes that you can make to the source code of
  26. Turbo Access to make your database virtually impervious to system crashes.
  27. Normally, when you use Turbo Access and you use MakeFile, AddRec, PutRec,
  28. DeleteRec, MakeIndex, AddKey, or DeleteKey to create or change a DataFile or
  29. IndexFile, not all of the changes you make are necessarily written to the disk
  30. right away.  Typically, the most recent changes are kept in memory buffers by
  31. Turbo Access, the Turbo run-time system, and/or DOS.  This technique allows
  32. file output to proceed more quickly, since an entire index page or disk sector
  33. can be written to the disk at once.  When a file gets closed, its memory
  34. buffers are all written to disk.  However, if the system should crash (due to
  35. program error, accidental Ctrl-Alt-Del, power failure, tripping over the plug,
  36. etc.) after a file has been changed and before it gets closed, the recent
  37. changes that are in memory but not on the disk will not get written.  Even more
  38. insidious, a system crash can mean that the file length in the directory entry,
  39. or the record numbers in IndexFiles, do not get written to disk, rendering the
  40. information in the database inconsistent and probably unusable.
  41.  
  42. One can guard against system crashes by flushing all information in a file's
  43. memory buffers out to disk, every time that the contents of that file changes.
  44. Unfortunately, using the routines provided by Turbo Access, a DataFile or
  45. IndexFile can only be flushed by closing and reopening the file.  This is
  46. primarily because DOS provides no explicitly documented way to flush a file's
  47. buffers without closing/reopening.  Closing and reopening a file every time its
  48. contents change is extraordinarily expensive at run-time, largely because DOS
  49. must perform a directory lookup every time a file is reopened.
  50.  
  51. Fortunately, there actually IS a little-known method for flushing a file's
  52. buffers and updating the file length in the file's directory entry under DOS,
  53. without reopening the file.  The technique for "flushing" a file is: 1) invoke
  54. DOS function 45H, "Duplicate a File Handle (DUP)," to duplicate the file
  55. handle, and then 2) invoke DOS function 3EH, "Close a File Handle" to close the
  56. extra file handle that you just created (this action does not close the
  57. original file handle).  The "Close" function 3EH flushes the file's buffers, as
  58. documented in the DOS Technical Reference manual.  Yet the original file handle
  59. is still valid and usable, so the file does not need to be reopened.  Thanks
  60. for this clever technique go to Dan Daetwyler in a letter to Ray Duncan's
  61. 16-Bit Software Toolbox column in the December '85 issue of Dr. Dobb's Journal.
  62.  
  63. One of the routines below is a new routine for use with Turbo Access, called
  64. FlushFile.  This procedure will perform an actual flush on any DataFile, so
  65. that the DataFile will be completely up-to-date and consistent even if the
  66. system should crash after the flush.  The FlushFile routine uses the technique
  67. described above.  Note: The FLUSH.PAS file, mentioned above, contains two
  68. routines for flushing standard Turbo files (rather than Turbo Access files).
  69. The FlushAny procedure in FLUSH.PAS is very similar to the FlushFile procedure
  70. below.  The differences are that FlushFile updates the internal Record 0
  71. maintained by Turbo Access, and that FlushFile uses TaIOcheck, the internal
  72. Turbo Access routine, to report errors, rather than writing the errors directly
  73. to the primary output.
  74.  
  75. Just as FlushFile will flush any DataFile, FlushIndex (the other procedure
  76. below) will flush any IndexFile.  The FlushIndex code is similar to the
  77. CloseIndex routine included with Turbo Access, with the following differences:
  78. the index pages are not removed from the page stack after they are written to
  79. disk (which can make subsequent index searches proceed faster), and the final
  80. call to CloseFile is replaced by a call to FlushFile.
  81.  
  82. After making the Turbo Access source code changes indicated below, FlushFile
  83. and FlushIndex may be called explicitly by the calling program whenever
  84. desired.  Alternatively, the calling program may set the global Boolean
  85. variable FailSafe to "true," and the (modified) Turbo Access routines will
  86. automatically invoke FlushFile or FlushIndex (depending on context) whenever a
  87. DataFile or IndexFile is changed.  By default, FailSafe is "false."  If you
  88. decide to set FailSafe to "true," it should be done immediately after the call
  89. to InitIndex at the beginning of your main program; i.e.:
  90.  
  91.       ...
  92.       InitIndex;
  93.       FailSafe := true;
  94.       ...
  95.  
  96. With the changes below, the only possibility that data can become corrupted due
  97. to a system crash is if the crash occurs between when the file gets changed and
  98. when FlushFile or FlushIndex is automatically (or explicitly) invoked
  99. immediately afterward.  That window of vulnerability is a small number of
  100. microseconds, and occurs only at the actual moment that the program outputs a
  101. new data record or index key.  If your program is reading records, searching
  102. the database, waiting for user input, computing, printing, updating the screen,
  103. etc., your database is still safe if the system crashes.  However, if there
  104. should be a power failure at the exact moment that data is being written, it is
  105. likely that the failure will also cause serious problems on the disk as a
  106. whole, trashing the FAT or some directories.  Thus, in this very unlikely
  107. event, the disk may need to be restored from backup tape or disks in any case,
  108. and you probably shouldn't lose sleep over this possibility.
  109.  
  110. There are two important DISadvantages to using the Turbo Access code changes
  111. below, assuming you have set FailSafe to "true":
  112.  
  113.      1) MakeFile, AddRec, PutRec, DeleteRec, MakeIndex, AddKey, and DeleteKey
  114. will all execute somewhat slower, because of the automatic file flush.  This
  115. delay will not be nearly as great as closing/reopening the file, but it can be
  116. noticeable if done frequently.  If your database application spends a
  117. significant amount of time updating records, you might want to test how much
  118. slower your application runs with FailSafe = "true" than with FailSafe =
  119. "false."  You can reduce the delay by living a little dangerously and running
  120. with FailSafe = "false," invoking FlushFile and FlushIndex explicitly at
  121. strategic points.
  122.  
  123.      2)  The file flushing code below only works with DOS.  If you plan to port
  124. your application to other operating systems later, you may want to build a more
  125. robust scheme for detecting and recovering from system crashes, since you won't
  126. be able take advantage of the crash protection below.
  127.  
  128. It would be wonderful if Turbo Access could be made to automatically flush file
  129. buffers under other operating systems, in addition to DOS.  If you find a way
  130. to do this, PLEASE PLEASE add this information to the FLUSH.ACC file
  131. accordingly, and re-upload to DL 1 of the Borland SIG!  For that matter, if you
  132. are feeling truly altruistic, you might want to make similar additions to
  133. FLUSH.PAS, too, showing how to flush normal Turbo files under other operating
  134. systems.
  135.  
  136. There are some other files in DL 1 of the Borland SIG that may be of interest.
  137. These files describe how to modify the Database Toolbox to extend it for
  138. various other purposes; e.g., EXTEND.ACC describes Toolbox modifications that
  139. allow one to open up to 96 Toolbox files simultaneously (252 under DOS 3.0 and
  140. higher) under DOS Turbo 3.0, and BIGTRE.BOX shows how to use more than 65535
  141. records per DataFile under DOS.  The modifications below have been tested with
  142. EXTEND.ACC, so you may use the FLUSH.ACC and EXTEND.ACC modifications together.
  143. However, the BIGTRE.BOX modifications have not yet been examined with respect
  144. to FLUSH.ACC and EXTEND.ACC.  Try a "BRO/KEY:TOOLBOX" and a "BRO/KEY:ACCESS" to
  145. find these and other files in DL 1.
  146.  
  147. Note: If you are using both the FLUSH.ACC modifications and the EXTEND.ACC
  148. modifications, you must apply the FLUSH.ACC modifications (below) first, THEN
  149. the EXTEND.ACC modifications.
  150.  
  151. Many thinks to Rick Amerson (CompuServe 72477,1566) for his generous help in
  152. testing the code below, to Andy Miller (CompuServe 70357,3656) for pointing out
  153. that the routines below do not use any undocumented features of DOS function
  154. calls, and to Peter Thomas (CompuServe 75716,2377) for suggesting the
  155. efficiency improvement to FlushIndex.
  156.  
  157. Change Log:
  158.  
  159.   Version 1.1: Removed caveats about undocumented use of DOS functions, since
  160.                this file actually only uses documented features of DOS.
  161.  
  162.   Version 1.2: Slight changes to FlushIndex so that index pages do not get
  163.                removed from the page stack in memory when they get flushed to
  164.                disk.  Can make subsequent index searches faster, since those
  165.                index pages will not need to be re-read from disk.
  166.  
  167.   Version 1.3: Updated to reflect the changes in Version 1.2 of the Database
  168.                Toolbox, and the new version of TBXFIX.
  169.  
  170. *******************************************************************************
  171.  
  172.  
  173. The code changes to add "flush" capability to MS-DOS Turbo Access Version 1.2
  174. are shown below.  Please BE SURE to make a backup copy of your Turbo Access
  175. source code before making these changes.  Note: When making the changes below,
  176. be sure to use the version of ACCESS.BOX intended for use with Turbo 3.0.  This
  177. file might be called ACCESS.BOX or ACCESS3.BOX on your Toolbox disk.  Check the
  178. comment header at the beginning of the file to ensure you are using the correct
  179. ACCESS.BOX.
  180.  
  181.  
  182.  
  183. STEP 1: Add the following declaration to ACCESS.BOX, immediately prior to
  184.         TaIOcheck:
  185.  
  186.              const
  187.                FailSafe: Boolean = false;
  188.  
  189.  
  190.  
  191. STEP 2: Insert the following routine immediately after the routine TaIOcheck in
  192.         ACCESS.BOX:
  193.  
  194.  
  195. {Flushes the buffers associated with the DataFile "DatF," and updates the file
  196.  length in the directory entry of "DatF," without closing "DatF."}
  197.  
  198. procedure FlushFile (var DatF: DataFile);
  199. var
  200.   handle: Integer absolute DatF; {File handle is the first word of a DataFile}
  201.   regs: record
  202.           case Integer of
  203.             1: (AX, BX, CX, DX, BP, SI, DI, DS, ES, Flags: Integer);
  204.             2: (AL, AH, BL, BH, CL, CH, DL, DH: Byte)
  205.         end;
  206. begin
  207.   {Code from beginning of CloseFile}
  208.   DatF.Int2 := DatF.NumRec;
  209.   Move(DatF.FirstFree, TaRecBuf, 8);
  210.  
  211.   {Simulate a PutRec(DatF, 0, TaRecBuf) (to avoid FlushFile/PutRec recursion)}
  212.   Seek(DatF.F, 0);
  213.   IOstatus := IOresult;
  214.   TaIOcheck(DatF, 0);
  215.   BlockWrite(DatF.F, TaRecBuf, 1);
  216.   IOstatus := IOresult;
  217.   TaIOcheck(DatF, 0);
  218.  
  219.   {Flush DatF}
  220.   IOstatus := $F0;            {"Disk write error" I/O error...just in case}
  221.   regs.AH := $45;             {DOS function to duplicate a file handle}
  222.   regs.BX := handle;
  223.   MsDos(regs);
  224.   if Odd(regs.Flags) then     {Check if carry flag is set}
  225.     TaIOcheck(DatF, 0);
  226.   regs.BX := regs.AX;         {Put new file handle into BX}
  227.   regs.AH := $3E;             {Dos function to close a file handle}
  228.   MsDos(regs);
  229.   if Odd(regs.Flags) then     {Check if carry flag is set}
  230.     TaIOcheck(DatF, 0)
  231. end {FlushFile};
  232.  
  233.  
  234.  
  235. STEP 3: Insert the following routine immediately after the routine OpenIndex in
  236.         ACCESS.BOX:
  237.  
  238.  
  239. {Flushes the buffers associated with the IndexFile "IdxF," and updates the file
  240.  length in the directory entry of "IdxF," without closing "IdxF."}
  241.  
  242. procedure FlushIndex (var IdxF: IndexFile);
  243. var
  244.   I: Integer;
  245. begin
  246.   {Similar to CloseIndex:}
  247.   for I := 1 to PageStackSize do
  248.     with TaPageStk[I] do
  249.       if (IndexFPtr = Addr(IdxF)) and Updated then
  250.       begin
  251.         TaPack(Page,IdxF.KeyL);
  252.  
  253.         {Simulate a PutRec(IdxF.DataF, PageRef, Page) (to avoid redundant
  254.          FlushFile calls)}
  255.         Seek(IdxF.DataF.F, PageRef);
  256.         IOstatus := IOresult;
  257.         TaIOcheck(IdxF.DataF, PageRef);
  258.         BlockWrite(IdxF.DataF.F, Page, 1);
  259.         IOstatus := IOresult;
  260.         TaIOcheck(IdxF.DataF, PageRef);
  261.  
  262.         TaUnpack(Page,IdxF.KeyL);
  263.         Updated := false
  264.       end;
  265.   IdxF.DataF.Int1 := IdxF.RR;
  266.   FlushFile(IdxF.DataF)
  267. end {FlushIndex};
  268.  
  269.  
  270.  
  271. STEP 4: Also in ACCESS.BOX, add the following line just before the "end" at the
  272.         end of the PutRec routine:
  273.  
  274.              if FailSafe then FlushFile(DatF);
  275.  
  276.  
  277.  
  278. STEP 5: Insert the following line just before the very last "end" statement at
  279.         the very end of the file ADDKEY.BOX:
  280.  
  281.              if FailSafe then FlushIndex(IdxF);
  282.  
  283.  
  284.  
  285. STEP 6: Also, insert the above line just before the very last "end" statement
  286.         at the very end of the file DELKEY.BOX.
  287.  
  288.  
  289.  
  290. STEP 7: Don't forget to add the line "FailSafe := true;", if desired, to your
  291.         main program, immediately after the call to InitIndex.
  292.  
  293.  
  294.  
  295. That's it!  If you have any questions, comments, or trouble with the above
  296. changes, please feel free to contact me on the Borland SIG or via EasyPlex on
  297. CompuServe.  I sincerely hope the above information is useful to you.
  298.  
  299. -- Randy Forgaard
  300.